feat(notifications): Linear dispatcher in fanout consumer (cost/turns/duration)#243
Conversation
|
Looking 👁️ |
Review — Linear dispatcher in fanout consumer (#239)Thanks for this — it's a clean, well-tested addition and it closes a real UX gap. The ABCA-91 case (agent crashes at A few things I especially liked:
Verdict: Approve with nits. One item I'd like scoped down before merge, plus some polish. Please address before merge1. actions: ['bedrock-agentcore:*'],
resources: ['*'],The handler only calls actions: [
'bedrock-agentcore:StartBrowserSession',
'bedrock-agentcore:StopBrowserSession',
'bedrock-agentcore:ConnectBrowserAutomationStream',
],If Non-blocking nits
TestsCoverage is solid and the suite passes locally (86 tests in
(The missing-OAuth-token graceful no-op, AC item 5, is covered at the helper layer in One small heads-up: since the PR is stacked on #241, the diff against Nice work overall — scope the IAM action and this is good to go. Reviewed with the help of automated agents (code-reviewer, silent-failure-hunter, pr-test-analyzer). 🤖 |
Welcome to Codecov 🎉Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests. Thanks for integrating Codecov - We've got you covered ☂️ |
|
@krokoko thanks for the review. All items addressed across two review-response commits. Shipped on this branchBlocking item (1) — Non-blocking nits (
Test gaps (
102 tests in Bonus from first dev smoke-test (
|
…ost/turns/duration Closes aws-samples#239. Adds a Linear dispatcher to the fanout consumer alongside the existing slack/github/email dispatchers. Posts a deterministic final-status comment on terminal task events for Linear-origin tasks, with cost, turns/max_turns, duration, and PR link rendered. Three framing modes by (event_type, pr_url): ✅ task_completed → 'Task completed'⚠️ error_max_turns + pr_url → 'Shipped a PR but stopped early' ❌ all other terminal states → 'Task <subtype>: <classifier title>' The⚠️ case is the motivating ABCA-91 scenario: agent hit max_turns on turn 101 after shipping PR aws-samples#35; previous behaviour was a silent ❌ reaction with no metrics surfaced to the requester. The platform-side comment fires deterministically even when the agent crashes (OOM, SDK buffer overflow, max_turns) before reaching its own step-3 completion comment. Architecture: - One new entry in NotificationChannel + CHANNEL_DEFAULTS + DISPATCHERS in fanout-task-events.ts. Dispatcher gates on channel_source === 'linear' so non-Linear tasks short-circuit after one DDB Get. - Reuses the existing postIssueComment helper from shared/linear-feedback.ts (already in use by the screenshot pipeline + orchestrator failure-reporting paths). - New construct props linearWorkspaceRegistryTable + linearOauthSecretArnPattern guard the IAM grants the same way slackSecretArnPattern does — a deployment without LinearIntegration gets no dangling permission to bgagent-linear-oauth-*. - FanOutConsumer instantiation moved below LinearIntegration in agent.ts so it can receive the registry table reference. Tests: 92 passing in fanout-task-events.test.ts (1816 across full CDK suite). New Linear-dispatcher describe block covers happy path, failed without PR, ABCA-91 max_turns-with-PR, channel_source short- circuit, missing metadata, and postIssueComment-returning-false graceful no-op.
Addresses the non-blocking nits from aws-samples#239 review: - JSDoc on renderLinearFinalStatusComment now describes the actual (eventType, prUrl) discriminator rather than 'error_max_turns' as an event type. The agent_status discrimination lives in the error classifier, not in the dispatcher's framing logic. - Inline comment on classifyError result corrected: returns null only for empty error_message, UNKNOWN_CLASSIFICATION (title 'Unexpected error') for any non-empty unmatched message. -⚠️ frame now appends classifier title — for ABCA-91 the requester sees 'Shipped a PR but stopped early — Exceeded max turns', not just the bare PR-shipped frame. - missing_env and post_failed log paths bumped from INFO to WARN with error_id tags so missing-env / post-failure are alarmable. The Linear comment is the only completion signal for the agent-crash case, so silent drops defeat the dispatcher's purpose. - Stale 'at most 3 channels' comment in routeEvent updated to 4. Test coverage: - New test: LINEAR_WORKSPACE_REGISTRY_TABLE_NAME unset → WARN + skip (the deploy-misconfig safety valve was unexercised). - New test: error_max_turns WITHOUT pr_url renders ❌, not⚠️ (the⚠️ ↔❌ boundary the other direction). - New describe block: 8 direct tests of renderLinearFinalStatusComment covering null-metric fallbacks, formatDuration boundaries (<60s, exact-minute Nm, mixed Nm Ss), classifier-title rendering on⚠️ and ❌ frames, and the no-trailing-colon when errorTitle is null. Total: 102 tests in fanout-task-events.test.ts (was 92), 1826 passing across the full CDK suite.
… dev smoke After the first dev deploy of the Linear dispatcher (aws-samples#239), two near-duplicate things showed up on the Linear thread: 1. The platform's ✅ comment carried PR: <url> while the agent's step-2 'PR opened' comment had already posted the same link one slot earlier. Two clickable copies of the same URL adds noise. 2. The agent's step-3 'task completed' free-form comment stacked right next to the platform's ✅ structured comment with full metrics. Two completion comments back-to-back with overlapping intent — the platform one is strictly more informative. Changes: - renderLinearFinalStatusComment: render PR url ONLY on the⚠️ shipped-but-stopped-early path. On ✅, the agent's step-2 comment is guaranteed to have fired with the PR link; on⚠️ the agent may have crashed before step-2 (e.g. ABCA-91 max-turns on turn 101), so the platform comment is the backup signal and the PR url has to be there. - Updated the corresponding test to assert not.toContain on the ✅ fixture's PR URL. - Removed step 3 from the Linear-channel prompt contract in prompt_builder.py. Replaced with an explicit prohibition against posting a final 'task completed/failed' comment, with a sentence pointing the agent at the platform fan-out plane (aws-samples#239) as the source of truth for terminal status. Net Linear thread shape post-task: agent posts start (1) + PR-opened (2); platform posts terminal ✅/⚠️ /❌ (3). One PR url, one completion comment. Krokoko predicted this exact migration in their PR-243 review — 'the agent prompt can drop step 3 once the platform side is reliable.' Targeted suite still 102 passing.
c9d17f1 to
8603cfa
Compare
Closes #239. Stacked on #241 (the screenshot pipeline) — review #241 first; this PR is the dispatcher that composes with it for the screenshot-from-Linear-issue feedback loop.
Summary
fanout-task-events.ts, mirroring the slack/github/email pattern. Single-entry registration via the existingNotificationChannel + CHANNEL_DEFAULTS + DISPATCHERSextension surface.postIssueCommenthelper; no new outbound Linear API surface.Architecture
NotificationChannel, 1 entry inCHANNEL_DEFAULTS(terminal events only), 1 entry inDISPATCHERSmap.dispatchToLineargates onchannel_source === 'linear'so non-Linear tasks short-circuit after oneDDB Get— same shape as the Slack dispatcher's gate.renderLinearFinalStatusCommentis exported for testability and cleanly separates the (event_type, pr_url) → header logic from the body assembly.linearWorkspaceRegistryTable,linearOauthSecretArnPattern) matching theslackSecretArnPatternpattern. A deployment without LinearIntegration gets no dangling IAM grants.FanOutConsumermoved belowLinearIntegrationinagent.tsso it can receive the registry table reference (LinearIntegration depends onruntimeandorchestrator, both already constructed earlier).Test plan
Out of scope (deferred per #239)
save_commentdoesn't support edit, so this is post-once on terminal events only